﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Threading;
using MacomberMapSystem.Common.Serialization;
using MacomberMapSystem.Common.Database;
using System.Data.Common;
using MacomberMapSystem.Common.User_Interfaces;

namespace MacomberMapSystem.Common.Internals
{
    /// <summary>
    /// This code checks to see whether the repository contains more up-to-date versions
    /// </summary>
    public static class MM_Update_Checker
    {
        /// <summary>The thread in which versions will be checked intermittently</summary>
        public static Thread CheckThread;

        /// <summary>Our responses to potential updates</summary>
        public enum enumUpgradeAction
        {
            /// <summary>No updates are available</summary>
            NoUpdatesAvailable,
            /// <summary>Ignore our updates</summary>
            IgnoreUpdates,
            /// <summary>User requested updates</summary>
            ProcessUpdates
        }


        /// <summary>
        /// Run our update thread, checking every interval
        /// </summary>
        /// <param name="state"></param>
        public static void RunUpdateThread(object state)
        {
            MM_Database_Connector Db = (MM_Database_Connector)(((object[])state)[0]);
            MM_Repository Repository = (MM_Repository)(((object[])state)[1]);
            Application.ApplicationExit += new EventHandler(Application_ApplicationExit);

            while (true)
            {
                Thread.Sleep(1000 * 60 * 60);
                try
                {
                    enumUpgradeAction Resp = CheckForUpdates(Db, Repository);
                    if (Resp == enumUpgradeAction.IgnoreUpdates || Resp == enumUpgradeAction.ProcessUpdates)
                        return;
                }
                catch { }

            }                    
        }



        /// <summary>
        /// Check to see whether updated versions of our libraries are online
        /// </summary>
        /// <param name="Db"></param>
        /// <param name="Repository"></param>
        /// <returns></returns>
        public static enumUpgradeAction CheckForUpdates(MM_Database_Connector Db, MM_Repository Repository)
        {
            //Look at our current list of assemblies, and determine their current versions, and whether they need to be updated
            Dictionary<String, Version[]> ToUpdate = new Dictionary<string, Version[]>();
            String RunDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);

            //Start by trying to delete all .bak files in the directory
            foreach (FileInfo fI in new DirectoryInfo(RunDirectory).GetFiles("*.bak*"))                
                try
                {
                    fI.Delete();
                }
                catch (Exception)
                {
                    Console.WriteLine("Unable to delete " + fI.Name);
                }

            StringBuilder sB = new StringBuilder();
            sB.AppendLine("The following components need to be updated:");


            //Build our list of all files and versions in our current directory
            foreach (FileInfo fI in new DirectoryInfo(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)).GetFiles("*.*", SearchOption.TopDirectoryOnly))
                if ((fI.Extension.Equals(".exe", StringComparison.CurrentCultureIgnoreCase) || fI.Extension.Equals(".dll", StringComparison.CurrentCultureIgnoreCase)) && !fI.Name.EndsWith(".vshost.exe", StringComparison.CurrentCultureIgnoreCase))
                    try
                    {
                        Version FileVer = new Version(FileVersionInfo.GetVersionInfo(fI.FullName).FileVersion);                        
                        List<Version> Versions = new List<Version>();
                        using (DbCommand dCmd = Db.CreateCommand("SELECT VERSION FROM MM_VERSIONS WHERE NAME=:0", fI.Name))
                        using (DbDataReader dRd = dCmd.ExecuteReader())
                            while (dRd.Read())
                                Versions.Add(new Version((string)dRd["VERSION"]));
                        Versions.Sort();
                        if (Versions.Count > 0 && FileVer < Versions[Versions.Count-1])
                        {
                            sB.AppendLine(fI.Name + "  (" + FileVer.ToString() + " => " + Versions[Versions.Count - 1].ToString() + ")");
                            List<Version> VersionUpdates = new List<Version>();
                            foreach (Version VersionToUpdate in Versions)
                                if (VersionToUpdate > FileVer)
                                    VersionUpdates.Add(VersionToUpdate);
                            ToUpdate.Add(fI.Name, VersionUpdates.ToArray());
                        }
                    }
                    catch (Exception)
                    { }
                               



            List<AssemblyName> AssembliesToCheck = new List<AssemblyName>();
            AssembliesToCheck.AddRange(Assembly.GetEntryAssembly().GetReferencedAssemblies());
            AssembliesToCheck.AddRange(Assembly.GetCallingAssembly().GetReferencedAssemblies());
            AssembliesToCheck.Add(Assembly.GetEntryAssembly().GetName());
            foreach (String str in ".dll;.exe".Split(';'))
            foreach (AssemblyName AssemblyToCheck in AssembliesToCheck)
                if (!ToUpdate.ContainsKey(AssemblyToCheck.Name + str))
                {
                    //First, check to see if we have this assembly in our database
                    List<Version> Versions = new List<Version>();
                    using (DbCommand dCmd = Db.CreateCommand("SELECT VERSION FROM MM_VERSIONS WHERE NAME=:0", AssemblyToCheck.Name + str))
                    using (DbDataReader dRd = dCmd.ExecuteReader())
                        while (dRd.Read())
                            Versions.Add(new Version((string)dRd["VERSION"]));
                    Versions.Sort();

                    //First, see if we have a missing file
                    if (Versions.Count > 0)
                        if (!File.Exists(Path.Combine(RunDirectory, AssemblyToCheck.Name + str)))
                        {
                            sB.AppendLine("Download: " + AssemblyToCheck.Name);                            
                            ToUpdate.Add(AssemblyToCheck.Name + str, Versions.ToArray());
                        }
                        else if (AssemblyToCheck.Version < Versions[Versions.Count - 1] && AssemblyName.GetAssemblyName(Path.Combine(RunDirectory, AssemblyToCheck.Name + str)).Version < Versions[Versions.Count - 1])
                        {
                            sB.AppendLine(AssemblyToCheck.Name + "  (" + AssemblyToCheck.Version.ToString() + " => " + Versions[Versions.Count - 1].ToString() + ")");
                            List<Version> UpdateVersions = new List<Version>();
                            foreach (Version VersionToUpdate in Versions)
                                if (VersionToUpdate > AssemblyToCheck.Version)
                                    UpdateVersions.Add(VersionToUpdate);
                            ToUpdate.Add(AssemblyToCheck.Name + str, UpdateVersions.ToArray());
                        }
                }


            

            
            //If we need updates, prompt the user, and handle accordingly
            if (ToUpdate.Count > 0)
            {
                DialogResult Resp;
                if (Environment.CommandLine.IndexOf("/autoupdate", StringComparison.CurrentCultureIgnoreCase) != -1)
                    Resp = DialogResult.Yes;
                else if (Environment.CommandLine.IndexOf("/ignoreupdate", StringComparison.CurrentCultureIgnoreCase) != -1)
                    Resp = DialogResult.No;
                else
                    using (MM_Update_Notification frmUpdate = new MM_Update_Notification(Repository, ToUpdate))
                        Resp = frmUpdate.ShowDialog();                            
                if (Resp == DialogResult.No)
                    return enumUpgradeAction.IgnoreUpdates;
                else if (Resp == DialogResult.Cancel)
                    return enumUpgradeAction.NoUpdatesAvailable;
                else
                {
                    foreach (KeyValuePair<String, Version[]> UpgradeMe in ToUpdate)
                    {
                        String TempFile = Path.Combine(RunDirectory, UpgradeMe.Key + ".bak");
                        if (File.Exists(TempFile))
                        {
                            int a = 0;
                            while (File.Exists(Path.Combine(RunDirectory, UpgradeMe.Key + ".bak" + (++a).ToString())))
                            { }                                
                        }                        
                        if (File.Exists(UpgradeMe.Key))
                            File.Move(Path.Combine(RunDirectory, UpgradeMe.Key), TempFile);
                        
                        using (DbCommand dCmd = Db.CreateCommand("SELECT DATA FROM MM_VERSIONS WHERE NAME=:0 AND VERSION=:1", UpgradeMe.Key, UpgradeMe.Value[UpgradeMe.Value.Length - 1].ToString()))
                        using (FileStream fS = new FileStream(Path.Combine(RunDirectory, UpgradeMe.Key), FileMode.CreateNew, FileAccess.Write))
                        {
                            byte[] inBytes = (byte[])dCmd.ExecuteScalar();
                            fS.Write(inBytes, 0, inBytes.Length);
                        }
                    }
                    ProcessStartInfo psi = new ProcessStartInfo(Environment.CommandLine);
                    psi.UseShellExecute = false;
                    Process.Start(psi);
                    Application.Exit();
                    return enumUpgradeAction.ProcessUpdates;
                }
            }
            else
                return enumUpgradeAction.NoUpdatesAvailable;
        }



        /// <summary>
        /// When the application exists, kill the checking thread
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void Application_ApplicationExit(object sender, EventArgs e)
        {
            if (CheckThread != null && CheckThread.ThreadState == System.Threading.ThreadState.Running)
                CheckThread.Abort();
        }
    }
}
